﻿// <copyright file="ClockFace.xaml.cs" company="ALCPU">
// Copyright (c) 2010 All Right Reserved
// </copyright>
// <author>Arthur Liberman</author>
// <email>Arthur_Liberman@hotmail.com</email>
// <date>04-04-2010</date>
// <summary>The clock's face code.</summary>

namespace Chess
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Timers;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.Windows.Threading;

    /// <summary>
    /// Describes the mode of the clock.
    /// </summary>
    public enum ClockMode
    {
        /// <summary>
        /// Analog clock, displaying the current time.
        /// </summary>
        Clock,

        /// <summary>
        /// Stopwatch mode.
        /// </summary>
        Stopwatch,

        /// <summary>
        /// Timer mode.
        /// </summary>
        Timer,

        /// <summary>
        /// Active mode while showing a replay of a game.
        /// </summary>
        Replay
    }

    /// <summary>
    /// Interaction logic for Clock.xaml
    /// </summary>
    public partial class ClockFace : UserControl
    {
        /// <summary>
        /// Dependancy property to allow animation of the Second hand twitch.
        /// </summary>
        private static readonly DependencyProperty secondAngleProperty = DependencyProperty.Register(
            "SecondAngle",
            typeof(double),
            typeof(ClockFace), 
            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// The clock timer.
        /// </summary>
        private DispatcherTimer timer;

        /// <summary>
        /// The current seconds value.
        /// </summary>
        private double seconds;

        /// <summary>
        /// A rectangle representing the second hand.
        /// </summary>
        private Rectangle secondHand;

        /// <summary>
        /// A rectangle representing the minute hand.
        /// </summary>
        private Rectangle minuteHand;

        /// <summary>
        /// A rectangle representing the hour hand.
        /// </summary>
        private Rectangle hourHand;

        /// <summary>
        /// The current value of the clock.
        /// </summary>
        private TimeSpan currentClock;

        /// <summary>
        /// The current time displayed on the clock when in Clock mode.
        /// </summary>
        private DateTime currentTime;

        /// <summary>
        /// The current clock mode.
        /// </summary>
        private ClockMode clockMode;

        /// <summary>
        /// Initializes a new instance of the <see cref="ClockFace"/> class.
        /// </summary>
        public ClockFace()
        {
            this.InitializeComponent();
            this.seconds = -1;
            this.clockMode = ClockMode.Clock;

            // Create a temporary timer, untill the Dispatcher creates the real one.
            this.timer = new DispatcherTimer();

            // We must let WPF calculate the size of the elements before we can go on 
            // and use them in the code. Therefore we must use the Dispatcher.
            this.clockFace.Dispatcher.BeginInvoke(
                new DispatcherOperationCallback(delegate(object arg)
                {
                    this.DrawClockFace();
                    this.timer = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 250), DispatcherPriority.Loaded, new EventHandler(this.Timer_Elapsed), this.clockFace.Dispatcher);
                    if (this.ClockMode != ClockMode.Clock)
                    {
                        this.timer.Stop();
                    }

                    return null;
                }),
                DispatcherPriority.Background,
                this);
        }

        /// <summary>
        /// Gets or sets the clock mode.
        /// </summary>
        /// <value>The clock mode.</value>
        public ClockMode ClockMode 
        {
            get
            {
                return this.clockMode; 
            }

            set 
            {
                if (value == ClockMode.Clock)
                {
                    this.timer.Start();
                }
                else
                {
                    this.timer.Stop();
                }

                this.clockMode = value; 
            }
        }

        /// <summary>
        /// Gets or sets the time.
        /// </summary>
        /// <value>The time on the clock.</value>
        public TimeSpan Time
        {
            get
            {
                return this.currentClock;
            }

            set
            {
                if (this.clockMode != ClockMode.Clock)
                {
                    this.SetTime(value);
                }
            } 
        }

        /// <summary>
        /// Gets or sets the second angle.
        /// </summary>
        /// <value>The second angle.</value>
        protected double SecondAngle 
        {
            get { return (double)this.GetValue(secondAngleProperty); }
            set { this.SetValue(secondAngleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the minute angle.
        /// </summary>
        /// <value>The minute angle.</value>
        protected double MinuteAngle 
        {
            get
            {
                double angle = 0;
                try
                {
                    angle = ((RotateTransform)((TransformGroup)this.minuteHand.RenderTransform).Children[1]).Angle;
                }
                catch (Exception)
                {
                }

                return angle;
            }

            set
            {
                try
                {
                    ((RotateTransform)((TransformGroup)this.minuteHand.RenderTransform).Children[1]).Angle = value;
                }
                catch (Exception)
                {
                }
            }
        }

        /// <summary>
        /// Gets or sets the hour angle.
        /// </summary>
        /// <value>The hour angle.</value>
        protected double HourAngle 
        {
            get
            {
                double angle = 0;
                try
                {
                    angle = ((RotateTransform)((TransformGroup)this.hourHand.RenderTransform).Children[1]).Angle;
                }
                catch (Exception)
                {
                }

                return angle;
            }

            set
            {
                try
                {
                    ((RotateTransform)((TransformGroup)this.hourHand.RenderTransform).Children[1]).Angle = value;
                }
                catch (Exception)
                {
                }
            }
        }

        /// <summary>
        /// Animates the second tick.
        /// </summary>
        protected void AnimateSecondTick()
        {
            DoubleAnimationUsingKeyFrames tick = new DoubleAnimationUsingKeyFrames();
            Storyboard storyboard = new Storyboard();
            tick.BeginTime = new TimeSpan(0);
            try
            {
                Storyboard.SetTargetName(tick, this.secondHand.Name);
                Storyboard.SetTargetProperty(tick, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[1].(RotateTransform.Angle)"));
            }
            catch (Exception)
            {
                return;
            }

            tick.KeyFrames.Add(new SplineDoubleKeyFrame(this.SecondAngle, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0))));
            tick.KeyFrames.Add(new SplineDoubleKeyFrame(this.SecondAngle + 3, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(50))));
            tick.KeyFrames.Add(new SplineDoubleKeyFrame(this.SecondAngle, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(50))));
            storyboard.Children.Add(tick);
            storyboard.Begin(this.secondHand);
        }

        /// <summary>
        /// Draws the clock face.
        /// </summary>
        protected void DrawClockFace()
        {
            double size = this.ActualWidth;
            if (size == 0)
            {
                return;
            }

            this.clockFace.Children.Clear();
            this.CreateHand(ref this.secondHand, "second", Brushes.Red, 133, 2.2, 3);
            this.CreateHand(ref this.minuteHand, "minute", Brushes.Black, 100, 2.7, 4);
            this.CreateHand(ref this.hourHand, "hour", Brushes.Black, 50, 3.5, 5);
            for (int i = 0; i < 60; ++i)
            {
                Rectangle marker = new Rectangle();
                if ((i % 5) == 0)
                {
                    marker.Width = size / 33.0;
                    marker.Height = size / 12.5;
                    marker.Fill = new SolidColorBrush(Color.FromArgb(0xe0, 0, 0, 0));
                    marker.Stroke = new SolidColorBrush(Color.FromArgb(0x80, 0x33, 0x33, 0x33));
                    marker.StrokeThickness = 0.5;
                }
                else
                {
                    marker.Width = size / 200.0;
                    marker.Height = size / 33.0;
                    marker.Fill = new SolidColorBrush(Color.FromArgb(0x80, 0, 0, 0));
                    marker.Stroke = null;
                    marker.StrokeThickness = 0;
                }

                TransformGroup transforms = new TransformGroup();

                transforms.Children.Add(new TranslateTransform(
                    -(marker.Width / 2),
                     (marker.Width / 2) - ((size / 2) - (size / 10)) - marker.Height));
                transforms.Children.Add(new RotateTransform(i * 6));
                transforms.Children.Add(new TranslateTransform((size / 2.0), (size / 2.0)));

                marker.RenderTransform = transforms;

                this.clockFace.Children.Add(marker);
            }

            for (int i = 1; i <= 12; ++i)
            {
                TextBlock textBlock = new TextBlock();

                textBlock.Text = i.ToString();
                textBlock.TextAlignment = TextAlignment.Center;
                textBlock.RenderTransformOrigin = new Point(1, 1);
                textBlock.Foreground = Brushes.Black;
                textBlock.FontSize = size / 25.0;

                textBlock.RenderTransform = new ScaleTransform(2, 2);

                double r = size / 3;
                double angle = Math.PI * i * 30.0 / 180.0;
                double x = (Math.Sin(angle) * r) + (size / 2.0),
                       y = -(Math.Cos(angle) * r) + (size / 2.0);

                Canvas.SetLeft(textBlock, x);
                Canvas.SetTop(textBlock, y);

                this.clockFace.Children.Add(textBlock);
            }

            this.clockFace.Children.Add(this.hourHand);
            this.clockFace.Children.Add(this.minuteHand);
            this.clockFace.Children.Add(this.secondHand);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.FrameworkElement.SizeChanged"/> event, using the specified information as part of the eventual event data.
        /// </summary>
        /// <param name="sizeInfo">Details of the old and new size involved in the change.</param>
        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);

            this.DrawClockFace();
        }

        /// <summary>
        /// Sets the time on the clock.
        /// </summary>
        /// <param name="time">The time to be set.</param>
        protected void SetTime(TimeSpan time)
        {
            this.SecondAngle = time.Seconds * 6;
            this.MinuteAngle = (time.Minutes * 6) + (time.Seconds / 10);
            this.HourAngle = (time.Hours * 30) + (time.Minutes / 2) + (time.Seconds / 120);
            this.currentClock = time;
            this.AnimateSecondTick();
        }

        /// <summary>
        /// Creates the clock hand.
        /// </summary>
        /// <param name="hand">Reference to the clock hand being created.</param>
        /// <param name="name">The name of the clock hand.</param>
        /// <param name="color">The color of the clock hand.</param>
        /// <param name="widthDiv">The width divider.</param>
        /// <param name="heightDiv">The height divider.</param>
        /// <param name="minWidth">Minimum width of the clock hand to have a border.</param>
        private void CreateHand(ref Rectangle hand, string name, Brush color, int widthDiv, double heightDiv, int minWidth)
        {
            double size = this.ActualWidth;
            string handName = name + "Hand";
            string angleName = Char.ToUpper(name[0]).ToString() + name.Substring(1) + "Angle";

            hand = new Rectangle();
            NameScope.SetNameScope(hand, new NameScope());
            hand.Name = handName;
            hand.RegisterName(handName, hand);
            hand.Width = size / widthDiv;
            hand.Height = size / heightDiv;
            hand.Fill = color;
            hand.Stroke = null;
            if (hand.Width >= minWidth)
            {
                hand.Stroke = new SolidColorBrush(Color.FromArgb(0xB0, 0xB0, 0xB0, 0xB0));
            }

            hand.RadiusX = hand.RadiusY = 2;
            hand.RenderTransformOrigin = new Point(0.0, 0.0);
            TransformGroup transformGroup = new TransformGroup();
            transformGroup.Children.Add(new TranslateTransform(-(hand.Width / 2), (hand.Width / 2) - hand.Height));
            transformGroup.Children.Add(new RotateTransform());
            transformGroup.Children.Add(new TranslateTransform((size / 2), (size / 2)));
            hand.SetBinding(RotateTransform.AngleProperty, new Binding(angleName));
            hand.RenderTransform = transformGroup;
        }

        /// <summary>
        /// Handles the Elapsed event of the timer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Timer_Elapsed(object sender, EventArgs e)
        {
            if (this.seconds != DateTime.Now.Second)
            {
                DateTime time = DateTime.Now;
                this.seconds = time.Second;
                this.SetTime(new TimeSpan(time.Hour, time.Minute, time.Second));
                this.AnimateSecondTick();
                this.currentTime = time;
            }
        }
    }
}